/*
 * Copyright (c) 2010 Zauber S.A.  -- All rights reserved
 */
package ar.edu.itba.ia2010.geneticos.motor.api.sintaxistree.bitwise;

import java.util.Iterator;
import java.util.ListIterator;
import java.util.Random;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.commons.lang.Validate;

import ar.edu.itba.ia2010.geneticos.motor.api.CacheableCromosoma;
import ar.edu.itba.ia2010.geneticos.motor.api.Cromosoma;
import ar.edu.itba.ia2010.geneticos.motor.api.Gen;
import ar.edu.itba.ia2010.geneticos.motor.api.MotorGeneticoContext;
import ar.edu.itba.ia2010.geneticos.motor.api.sintaxistree.Expresion;
import ar.edu.itba.ia2010.geneticos.motor.api.sintaxistree.Expresion.VisitorCallback;
import ar.edu.itba.ia2010.geneticos.motor.api.sintaxistree.bitwise.visitors.BitwiseExpresionEditor;
import ar.edu.itba.ia2010.geneticos.motor.api.sintaxistree.bitwise.visitors.CountVisitorCallback;
import ar.edu.itba.ia2010.geneticos.motor.api.sintaxistree.bitwise.visitors.EditorEvalVisitorCallback;

/**
 * Cromosoma que usa arboles sintaxicos
 * 
 * @author Juan F. Codagnone
 * @since Mar 28, 2010
 */
public class ExpresionCromosoma implements CacheableCromosoma<Expresion>  {
    private Expresion e;
    private Double aptitud;
    
    /** Creates the BitwiseCromosoma. */
    public ExpresionCromosoma(final Expresion e) {
        Validate.notNull(e);
        
        this.e = e;
    }
    
    /** @see Cromosoma#crossover(Random, Cromosoma) */
    public final Cromosoma<Expresion>[] crossover(final Random random,
            final Cromosoma<Expresion> other) {
        final ExpresionCromosoma pareja = (ExpresionCromosoma) other;

        // cuento cuantos nodos hay; asi conozco el maximo para el rand
        final CountVisitorCallback i = new CountVisitorCallback();
        final CountVisitorCallback j = new CountVisitorCallback();
        e.preorder(i);
        pareja.e.preorder(j);
        
        // elijo punto de cruze
        final int cutPoint1 = random.nextInt(i.getI());
        final int cutPoint2 = random.nextInt(j.getI());

        // clono hasta el punto de cruce
        final Expresion.DeepCopyContext ctx1 = 
            new Expresion.DeepCopyContext(cutPoint1);
        final Expresion.DeepCopyContext ctx2 = 
            new Expresion.DeepCopyContext(cutPoint2);
        final Expresion s3 =  e.deepCopy(ctx1);
        final Expresion s4 =  pareja.e.deepCopy(ctx2);
        if(cutPoint1 == 0) {
            ctx1.subtree = pareja.e.deepCopy(null); 
        }
        if(cutPoint2 == 0) {
            ctx2.subtree = e.deepCopy(null); 
        }
        
        // Remplazo el wildcard
        Expresion t = s3.preorder(new Expresion.VisitorCallback() {
            public Expresion notify(final Expresion expr) {
                if(expr instanceof ReplaceExpresion) {
                    return ctx2.subtree;
                }
                return null;
            }
        });
        final Expresion ss3 =  t == null ? s3 : t;
        t = s4.preorder(new Expresion.VisitorCallback() {
            public Expresion notify(final Expresion expr) {
                if(expr instanceof ReplaceExpresion) {
                    return ctx1.subtree;
                }
                return null;
            }
        });
        final Expresion ss4 =  t == null ? s4 : t;
        
        final Expresion.VisitorCallback validate = new Expresion.VisitorCallback() {
            public Expresion notify(final Expresion expr) {
                if(expr instanceof ReplaceExpresion) {
                    throw new IllegalStateException("No puede haber ?: "
                             + "\ns1: " + e.toString()
                             + "\ns2: " + pareja.e.toString()
                             + "\ncut1: " + cutPoint1
                             + "\ncut2: " + cutPoint2
                             + "\ns3: " + ss3.toString()
                             + "\ns4: " + ss4.toString()
                             + "\nctx1: " + ctx1.subtree
                             + "\nctx2: " + ctx2.subtree
                             );
                }
                return null;
            }
        };
        ss3.preorder(validate);
        ss4.preorder(validate);
        
        final Cromosoma<Expresion>[] ret = new Cromosoma[2];
        ret[0] = new ExpresionCromosoma(ss3);
        ret[1] = new ExpresionCromosoma(ss4);

        // TODO estos tal vez no van 
        setAptitud(null);
        ((CacheableCromosoma<Expresion>)other).setAptitud(null);
        return ret;
    }

    /** @see Cromosoma#mutar(Random, double) */
    public final Cromosoma<Expresion> mutar(final Random random, 
            final double probabilidad, final MotorGeneticoContext ctx) 
            throws IllegalArgumentException {
        final AtomicBoolean muted = new AtomicBoolean(false);
        
        final Expresion expr = e.preorder(new Expresion.VisitorCallback() {
            public Expresion notify(final Expresion exp) {
                final Expresion ret = (Expresion) exp.mutar(random, probabilidad,
                        ctx);
                if(ret != null) {
                    muted.set(true);
                }
                return ret;
            }
        });
        if(expr != null) {
            e = expr;
            muted.set(true);
        }
        if(muted.get()) {
            setAptitud(null);
        }

        return this;
    }

    /** @see Iterable#iterator() */
    public final Iterator<Gen<Expresion>> iterator() {
        return listiterator();
    }


    /** @see Cromosoma#listiterator() */
    public final ListIterator<Gen<Expresion>> listiterator() {
        return new VarArgIterator<Gen<Expresion>>(e);
    }
    
    /** @see CacheableCromosoma#getAptitud() */
    public final Double getAptitud() {
        return aptitud;
    }
    
    private String s;
    /** @see CacheableCromosoma#setAptitud(Double) */
    public final void setAptitud(final Double aptitud) {
        this.aptitud = aptitud;
        if(aptitud != null) {
            s = toString();
        } else {
            s = null;
        }
        
    }
    
    
    /** @see Object#toString() */
    @Override
    public final String toString() {
        return e.toString();
    }
    
    
    /**  @see Expresion#reducir() */
    public final void reducir(final boolean[][] cases) {
        setAptitud(null);
        
        
        // puede  ser que haya quedado un true o un false (si toda la expresion
        // es verdadera o toda es falsa)
        final Variable a = new Variable(0);
        e = e.posorder(new BitwiseExpresionEditor());
        Expresion tmp = e.posorder(new EditorEvalVisitorCallback(cases));
        if(tmp != null) {
            e = tmp;
        }
        e = e.posorder(new BitwiseExpresionEditor());
        
        // reemplazo true y false
        e.posorder(
                new VisitorCallback() {
            public Expresion notify(final Expresion ee) {
                Expresion ret = ee;
                if(ee instanceof True) {
                    ret = new Or(a, new Not(a));
                } else if(ee instanceof False) {
                    ret = new And(a, new Not(a));
                }
                
                return ret;
            }
        });
    }
}
